package com.lihaichen.utils.soap;

import java.io.ByteArrayOutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPConstants;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class includes utility methods for handling general SOAP client.
 * 
 * <p> It helps the replacement of generated web service stubs to loose couple
 *     web service and client</p>
 * 
 * @author  Richard Li 
 * @since   Apr 2012
 * @version 1.1
 * 
 */
public abstract class SoapHelper {

	private static final Logger logger = LoggerFactory.getLogger(SoapHelper.class.getName());
	
        private static final String DEFAULT_WSA_NAMESPACE="http://www.w3.org/2005/08/addressing";
        private static final String DEFAULT_WSA_PREFIX="wsa";
    		
	private String nameSpace;
	private String prefix;
	private String urlLink;
	private String webEndpoint; //TODO revisit this to clarify the correct name as needed.

	private boolean printEnabled = false;
	protected boolean isSOAP12 = true;
	
	//======================================================================
	// constructors as follows:
	//======================================================================
		
	public SoapHelper() {

	}

	public SoapHelper(String nameSpace, String prefix) {
		this.nameSpace = nameSpace;
		this.prefix = prefix;
	}

	public SoapHelper(String nameSpace, String prefix, String urlLink) {
		this.nameSpace = nameSpace;
		this.prefix = prefix;
		this.urlLink = urlLink;
	}
	
	public SoapHelper(String nameSpace, String prefix, String urlLink, String webEndpoint) {
		this.nameSpace = nameSpace;
		this.prefix = prefix;
		this.urlLink = urlLink;
		this.webEndpoint= webEndpoint;		
	}
	
	//======================================================================
	// main methods as follows:
	//======================================================================
		
	public SOAPMessage callWS(SOAPMessage message) throws SOAPException {

		SOAPMessage response = null;
		try {
                            response = callWS(message, new URL(getEndpointAddress()));
                    } catch (MalformedURLException e) {
                            logger.error("Caught exception " , e);
                    }
		if(printEnabled) printMessage(response);
		return response;

	}

	public SOAPMessage callWS(SOAPMessage message, URL endpoint) throws SOAPException {

		SOAPConnection connection = null;
		SOAPMessage response = null;
		logger.info("\n\n[endpoint]="+endpoint+"\n");
		if(printEnabled) printMessage(message);
		try {
			    SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory.newInstance();
                            connection = soapConnectionFactory.createConnection();
                            logger.info("calling.....");
                            response = connection.call(message, endpoint);
                            logger.info("called, no exception");
	
			} catch (UnsupportedOperationException e) {
				logger.error("Caught exception " , e);
                                throw e;
			} catch (SOAPException e) {
				logger.error("Caught exception " , e);
                                throw e;
			} finally {
				if(connection!=null) {
                                    try {
                                                connection.close();
                                        } catch (Exception e) {
                                                logger.error("Caught exception " , e);
                                        }
				}
			}
		    return response;					
	}


	//======================================================================
	// utility methods as follows:
	//======================================================================
	
	protected SOAPMessage getSoapMessageTemplate(String soapAction) throws SOAPException {
		SOAPMessage message;
		MessageFactory factory = null;
		if (this.isSOAP12) {
			factory = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL);
		} else {
			factory = MessageFactory.newInstance();
		}
		
		message = factory.createMessage();

		// build header
                logger.debug("soapAction="+soapAction);
                if(null!=soapAction && soapAction.trim().length()>0) {
                    SOAPHeader header = message.getSOAPHeader();
                    header.addNamespaceDeclaration(DEFAULT_WSA_PREFIX, DEFAULT_WSA_NAMESPACE);
                    SOAPHeaderElement wsaAction = header.addHeaderElement(getWSAActionQName());
                    wsaAction.setTextContent(getNameSpaceSoapAction(soapAction));
                    SOAPHeaderElement wasTo= header.addHeaderElement(getWSAToQName());
                    wasTo.setTextContent(getEndpointAddress());
                }
		return message;
	}
	
        private String getNameSpaceSoapAction(String soapAction){
            
                StringBuilder returnValue = new StringBuilder();
                returnValue.append(getNameSpace());
                if(!nameSpace.endsWith("/")){
                   returnValue.append("/");
                }
                returnValue.append(soapAction);
                return returnValue.toString();
        }


	protected String getEndpointAddress(){
		
		if(null==getWebEndpoint() || getWebEndpoint().trim().length()==0){
			return getUrlLink();
		}else{
			return getUrlLink()+"/"+getWebEndpoint();	
		}	
	}
	
	protected QName getQName(String localName){
		return new QName(getNameSpace(), localName, getPrefix());
	}
        
        private QName getQNameNoPrefix(String localName){
		return new QName(localName);
	}

        protected QName getQName(String localName, boolean isPrefix){
                QName qName = null;
                if(isPrefix){
                    qName = getQName(localName);
                } else{
                    qName = getQNameNoPrefix(localName);
                }
		return qName;
	}
	
	private QName getWSAToQName(){
		return new QName(DEFAULT_WSA_NAMESPACE, "To", DEFAULT_WSA_PREFIX);
	}
	
	private QName getWSAActionQName(){
		return new QName(DEFAULT_WSA_NAMESPACE, "Action", DEFAULT_WSA_PREFIX);
	}
	
        protected <T extends SOAPElement> T addChildElement(
                String name, T bodyElement) throws SOAPException {
                return (T) bodyElement.addChildElement(getQName(name));
	}
        
        protected <T extends SOAPElement> void addChildElement(
                String value, String name, T bodyElement) throws SOAPException {
                T symbol = (T) bodyElement.addChildElement(getQName(name));
		symbol.addTextNode(value);
	}
        
        protected <T extends SOAPElement> void addChildElement(
                String value, String name, T bodyElement, boolean isQName) throws SOAPException {
                T symbol = (T) bodyElement.addChildElement(getQName(name,isQName));
		symbol.addTextNode(value);
	}

        protected void addChildElementNoPrefix(String value, String name,
			SOAPBodyElement bodyElement) throws SOAPException {
                addChildElement(value,name,bodyElement,false);
	}
		
	public void printMessage(SOAPMessage message) {
		logger.info(getSOAPMessageAsString(message,true));
	}
        public String printMessageAsString(SOAPMessage message) {
	       return getSOAPMessageAsString(message,true);
	}
	
	public String getSOAPMessageAsString(SOAPMessage soapMessage, boolean isPretty) {
		
            if(null==soapMessage) return null;
		
	    try {
	    	 TransformerFactory tff = TransformerFactory.newInstance();
	         Transformer tf = tff.newTransformer();

	         // Set formatting
	         if(isPretty){
		         tf.setOutputProperty(OutputKeys.INDENT, "yes");
		         tf.setOutputProperty("{http://xml.apache.org/xslt}indent-amount","2");
	         }
	         Source sc = soapMessage.getSOAPPart().getContent();
	         ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
	         StreamResult result = new StreamResult(streamOut);
	         tf.transform(sc, result);
	         String strMessage = streamOut.toString();
	         return strMessage;
	      } catch (Exception e) {
	         logger.error("Caught exception " , e);
	         return null;
	      }
	   }

        public String getCauseMessage(SOAPException ex){
            String returnValue = null;
            Throwable _cause = ex.getCause();
            while (null!=_cause){
                returnValue = _cause.getMessage();
                logger.error("Message {}",returnValue);
                _cause = _cause.getCause();
            }
            return returnValue;
        }
	
	//======================================================================
	// getters and setters as follows:
	//======================================================================
	
	/**
	 * @return the nameSpace
	 */
	public String getNameSpace() {
		return nameSpace;
	}

	/**
	 * @param nameSpace
	 *            the nameSpace to set
	 */
	public void setNameSpace(String nameSpace) {
		this.nameSpace = nameSpace;
	}

	/**
	 * @return the urlLink
	 */
	public String getUrlLink() {
		return urlLink;
	}

	/**
	 * @param urlLink
	 *            the urlLink to set
	 */
	public void setUrlLink(String urlLink) {
		this.urlLink = urlLink;
	}

	/**
	 * @return the prefix
	 */
	public String getPrefix() {
		return prefix;
	}

	/**
	 * @param prefix
	 *            the prefix to set
	 */
	public void setPrefix(String prefix) {
		this.prefix = prefix;
	}

	/**
	 * @return the webEndpoint
	 */
	public String getWebEndpoint() {
		return webEndpoint;
	}

	/**
	 * @param webEndpoint the webEndpoint to set
	 */
	public void setWebEndpoint(String webEndpoint) {
		this.webEndpoint = webEndpoint;
	}

	/**
	 * @return the printEnabled
	 */
	public boolean isPrintEnabled() {
		return printEnabled;
	}

	/**
	 * @param printEnabled the printEnabled to set
	 */
	public void setPrintEnabled(boolean printEnabled) {
		this.printEnabled = printEnabled;
	}

}
